/*-------------------------------------------------------------------------
 *
 * deparse_view_stmts.c
 *
 *	  All routines to deparse view statements.
 *
 * Copyright (c), Citus Data, Inc.
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "catalog/namespace.h"
#include "commands/defrem.h"
#include "lib/stringinfo.h"
#include "nodes/parsenodes.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"

#include "distributed/citus_ruleutils.h"
#include "distributed/commands.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"

static void AppendDropViewStmt(StringInfo buf, DropStmt* stmt);
static void AppendViewNameList(StringInfo buf, List* objects);
static void AppendAlterViewStmt(StringInfo buf, AlterTableStmt* stmt);
static void AppendAlterViewCmd(StringInfo buf, AlterTableCmd* alterTableCmd);
static void AppendAlterViewOwnerStmt(StringInfo buf, AlterTableCmd* alterTableCmd);
static void AppendAlterViewSetOptionsStmt(StringInfo buf, AlterTableCmd* alterTableCmd);
static void AppendAlterViewResetOptionsStmt(StringInfo buf, AlterTableCmd* alterTableCmd);
static void AppendRenameViewStmt(StringInfo buf, RenameStmt* stmt);
static void AppendAlterViewSchemaStmt(StringInfo buf, AlterObjectSchemaStmt* stmt);

/*
 * DeparseDropViewStmt deparses the given DROP VIEW statement.
 */
char* DeparseDropViewStmt(Node* node)
{
    DropStmt* stmt = castNode(DropStmt, node);
    StringInfoData str = {0};
    initStringInfo(&str);

    Assert(stmt->removeType == OBJECT_VIEW);

    AppendDropViewStmt(&str, stmt);

    return str.data;
}

/*
 * AppendDropViewStmt appends the deparsed representation of given drop stmt
 * to the given string info buffer.
 */
static void AppendDropViewStmt(StringInfo buf, DropStmt* stmt)
{
    /*
     * already tested at call site, but for future it might be collapsed in a
     * DeparseDropStmt so be safe and check again
     */
    Assert(stmt->removeType == OBJECT_VIEW);

    appendStringInfo(buf, "DROP VIEW ");
    if (stmt->missing_ok) {
        appendStringInfoString(buf, "IF EXISTS ");
    }
    AppendViewNameList(buf, stmt->objects);
    if (stmt->behavior == DROP_CASCADE) {
        appendStringInfoString(buf, " CASCADE");
    }
    appendStringInfoString(buf, ";");
}

/*
 * AppendViewNameList appends the qualified view names by constructing them from the given
 * objects list to the given string info buffer. Note that, objects must hold schema
 * qualified view names as its' members.
 */
static void AppendViewNameList(StringInfo buf, List* viewNamesList)
{
    bool isFirstView = true;
    List* qualifiedViewName = NULL;
    foreach_declared_ptr(qualifiedViewName, viewNamesList)
    {
        char* quotedQualifiedVieName = NameListToQuotedString(qualifiedViewName);
        if (!isFirstView) {
            appendStringInfo(buf, ", ");
        }

        appendStringInfoString(buf, quotedQualifiedVieName);
        isFirstView = false;
    }
}

/*
 * DeparseAlterViewStmt deparses the given ALTER VIEW statement.
 */
char* DeparseAlterViewStmt(Node* node)
{
    AlterTableStmt* stmt = castNode(AlterTableStmt, node);
    StringInfoData str = {0};
    initStringInfo(&str);

    AppendAlterViewStmt(&str, stmt);

    return str.data;
}

static void AppendAlterViewStmt(StringInfo buf, AlterTableStmt* stmt)
{
    const char* identifier =
        quote_qualified_identifier(stmt->relation->schemaname, stmt->relation->relname);

    appendStringInfo(buf, "ALTER VIEW %s ", identifier);

    AlterTableCmd* alterTableCmd = castNode(AlterTableCmd, lfirst(list_head(stmt->cmds)));
    AppendAlterViewCmd(buf, alterTableCmd);

    appendStringInfoString(buf, ";");
}

static void AppendAlterViewCmd(StringInfo buf, AlterTableCmd* alterTableCmd)
{
    switch (alterTableCmd->subtype) {
        case AT_ChangeOwner: {
            AppendAlterViewOwnerStmt(buf, alterTableCmd);
            break;
        }

        case AT_SetRelOptions: {
            AppendAlterViewSetOptionsStmt(buf, alterTableCmd);
            break;
        }

        case AT_ResetRelOptions: {
            AppendAlterViewResetOptionsStmt(buf, alterTableCmd);
            break;
        }

        case AT_ColumnDefault: {
            elog(ERROR, "Citus doesn't support setting or resetting default values for a "
                        "column of view");
            break;
        }

        default: {
            /*
             * ALTER VIEW command only supports for the cases checked above but an
             * ALTER TABLE commands targeting views may have different cases. To let
             * PG throw the right error locally, we don't throw any error here
             */
            break;
        }
    }
}

static void AppendAlterViewOwnerStmt(StringInfo buf, AlterTableCmd* alterTableCmd)
{
    appendStringInfo(buf, "OWNER TO %s", RoleSpecString(alterTableCmd->name, true));
}

static void AppendAlterViewSetOptionsStmt(StringInfo buf, AlterTableCmd* alterTableCmd)
{
    ListCell* lc = NULL;
    bool initialOption = true;
    foreach (lc, (List*)alterTableCmd->def) {
        DefElem* def = (DefElem*)lfirst(lc);

        if (initialOption) {
            appendStringInfo(buf, "SET (");
            initialOption = false;
        } else {
            appendStringInfo(buf, ",");
        }

        appendStringInfo(buf, "%s", def->defname);
        if (def->arg != NULL) {
            appendStringInfo(buf, "=");
            appendStringInfo(buf, "%s", defGetString(def));
        }
    }

    appendStringInfo(buf, ")");
}

static void AppendAlterViewResetOptionsStmt(StringInfo buf, AlterTableCmd* alterTableCmd)
{
    ListCell* lc = NULL;
    bool initialOption = true;
    foreach (lc, (List*)alterTableCmd->def) {
        DefElem* def = (DefElem*)lfirst(lc);

        if (initialOption) {
            appendStringInfo(buf, "RESET (");
            initialOption = false;
        } else {
            appendStringInfo(buf, ",");
        }

        appendStringInfo(buf, "%s", def->defname);
    }

    appendStringInfo(buf, ")");
}

char* DeparseRenameViewStmt(Node* node)
{
    RenameStmt* stmt = castNode(RenameStmt, node);
    StringInfoData str = {0};
    initStringInfo(&str);

    AppendRenameViewStmt(&str, stmt);

    return str.data;
}

static void AppendRenameViewStmt(StringInfo buf, RenameStmt* stmt)
{
    switch (stmt->renameType) {
        case OBJECT_COLUMN: {
            const char* identifier = quote_qualified_identifier(
                stmt->relation->schemaname, stmt->relation->relname);
            appendStringInfo(buf, "ALTER VIEW %s RENAME COLUMN %s TO %s;", identifier,
                             quote_identifier(stmt->subname),
                             quote_identifier(stmt->newname));
            break;
        }

        case OBJECT_VIEW: {
            const char* identifier = quote_qualified_identifier(
                stmt->relation->schemaname, stmt->relation->relname);
            appendStringInfo(buf, "ALTER VIEW %s RENAME TO %s;", identifier,
                             quote_identifier(stmt->newname));
            break;
        }

        default: {
            ereport(ERROR, (errmsg("unsupported subtype for alter view rename command"),
                            errdetail("sub command type: %d", stmt->renameType)));
        }
    }
}

char* DeparseAlterViewSchemaStmt(Node* node)
{
    AlterObjectSchemaStmt* stmt = castNode(AlterObjectSchemaStmt, node);
    StringInfoData str = {0};
    initStringInfo(&str);

    AppendAlterViewSchemaStmt(&str, stmt);

    return str.data;
}

static void AppendAlterViewSchemaStmt(StringInfo buf, AlterObjectSchemaStmt* stmt)
{
    const char* identifier =
        quote_qualified_identifier(stmt->relation->schemaname, stmt->relation->relname);
    appendStringInfo(buf, "ALTER VIEW %s SET SCHEMA %s;", identifier,
                     quote_identifier(stmt->newschema));
}
